from typing import List, Dict, Any, DefaultDict, Optional
from collections import defaultdict
from pathlib import Path
import logging, os
from dotenv import load_dotenv

from assistant.models import Product, User


class ProductRecommendationAssistant:
    """
    轻量级“神经网络思路”加权推荐器：
    - 类别频率 ≈ 神经元权重
    - 价格接近度 ≈ 激活程度
    """

    # ---------- 工具 ---------- #
    @staticmethod
    def _as_dict(model: Any) -> Dict[str, Any]:
        """
        兼容 Pydantic v1 (`dict()`) 和 v2 (`model_dump()`).
        """
        if hasattr(model, "model_dump"):
            return model.model_dump()
        return model.dict()

    def __init__(self) -> None:
        # 环境 & 日志
        env_path = Path(__file__).resolve().parent.parent / ".env"
        if env_path.exists():
            load_dotenv(env_path)
        self.debug = os.getenv("DEBUG", "false").lower() == "true"

        self.logger = logging.getLogger("recommend-assistant")
        self.logger.setLevel(logging.DEBUG if self.debug else logging.INFO)
        if not self.logger.handlers:
            h = logging.StreamHandler()
            h.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
            self.logger.addHandler(h)

        # 内部存储
        self._products: Dict[int, Product] = {}
        self._users: Dict[str, User] = {}
        self.logger.info("ProductRecommendationAssistant ready")

    # ---------- 数据加载与动态写入 ---------- #
    def load_data(self, users: List[User], products: List[Product]) -> None:
        self._users = {u.username: u for u in users}
        self._products = {p.id: p for p in products}
        self.logger.info("Loaded %d users, %d products", len(users), len(products))

    def add_product(self, product: Product) -> None:
        self._products[product.id] = product
        self.logger.debug("Added product %s", product.name)

    def add_purchase(self, username: str, product_id: int) -> None:
        if username not in self._users:
            raise ValueError("User not found")
        if product_id not in self._products:
            raise ValueError("Product not found")
        self._users[username].purchased_product_ids.append(product_id)
        self.logger.debug("User %s purchased product %d", username, product_id)

    # ---------- 推荐主入口 ---------- #
    def recommend_products(
        self, username: str, top_k: int = 5
    ) -> List[Dict[str, Any]]:
        if username not in self._users:
            raise ValueError("User not found")

        user = self._users[username]
        purchased_ids = set(user.purchased_product_ids)
        if not purchased_ids:
            return self._popular_products(top_k)

        cat_cnt: DefaultDict[str, int] = defaultdict(int)
        prices: List[float] = []
        for pid in purchased_ids:
            p = self._products.get(pid)
            if p:
                cat_cnt[p.category] += 1
                prices.append(p.price)

        total_cnt = sum(cat_cnt.values())
        avg_price = sum(prices) / len(prices) if prices else 0.0

        scored: List[tuple[float, Product]] = []
        for p in self._products.values():
            if p.id in purchased_ids:
                continue

            cat_score = cat_cnt.get(p.category, 0) / total_cnt if total_cnt else 0
            price_score = (
                max(0.0, 1 - abs(p.price - avg_price) / avg_price) if avg_price else 0
            )
            score = 0.7 * cat_score + 0.3 * price_score
            scored.append((score, p))

        scored.sort(key=lambda x: x[0], reverse=True)
        return [self._as_dict(p) for _, p in scored[:top_k]]

    # ---------- 新增：用户画像（用于大模型推荐理由） ---------- #
    def describe_user(self, username: str) -> str:
        user = self._users.get(username)
        if not user:
            return "No purchase history."

        categories = []
        prices = []

        for pid in user.purchased_product_ids:
            product = self._products.get(pid)
            if product:
                categories.append(product.category)
                prices.append(product.price)

        if not categories or not prices:
            return "User has no meaningful purchase history."

        top_category = max(set(categories), key=categories.count)
        avg_price = sum(prices) / len(prices)

        return f"The user prefers category '{top_category}' and usually spends around ¥{avg_price:.2f}."

    # ---------- util ---------- #
    def _popular_products(self, k: int) -> List[Dict[str, Any]]:
        pop_count: DefaultDict[int, int] = defaultdict(int)
        for u in self._users.values():
            for pid in u.purchased_product_ids:
                pop_count[pid] += 1
        ranked = sorted(
            (self._products[pid] for pid in pop_count if pid in self._products),
            key=lambda p: pop_count[p.id],
            reverse=True,
        )
        return [self._as_dict(p) for p in ranked[:k]]

    def get_product_details(self, name: str) -> dict | None:
        for p in self._products.values():
            if p.name.lower() == name.lower():
                return self._as_dict(p)
        return None

    # ---------- 新增：供后端查询 ---------- #
    def get_user_data(self, username: str) -> Optional[User]:
        return self._users.get(username)

    def get_product_by_id(self, product_id: int) -> Optional[Product]:
        return self._products.get(product_id)

    def health_check(self) -> Dict[str, Any]:
        return {
            "status": "ok",
            "users": len(self._users),
            "products": len(self._products),
        }
